#include "stdafx.h"

#include "Game.h" 
#include "state/RunningState.h"
#include "state/PendingState.h"
#include "state/State.h"
#include "network/Communicator.h"
#include "graphics/Primitives.hpp"
#include "graphics/ParticleSystem.h"

glm::dvec2 Game::windowDim(1024.0, 768.0);
Game::GameType game_type = Game::GameType::SINGLE;
Game* Game::instance = nullptr;
GLFWwindow* Game::window = nullptr;

Game* Game::create(GameType isServer) {
	assert(!instance);
	instance = new Game(isServer);
	return instance;
}
Game* Game::getInstance() {
	assert(instance);
	return instance;
}

Game::Game(GameType game_type) : game_type(game_type) {
	communicator = new Communicator(game_type == Game::SERVER);
	// put opengl stuff after opengl init in init() not here 
}

Game::~Game() {
	for ( auto it = stateLUT.begin(); it != stateLUT.end(); ++it )
		delete (*it).second;
	delete communicator;
}

void Game::init() {
    if (!glfwInit())
        throw std::runtime_error("Failed in glfw init\n");
    //glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE, GL_TRUE);
	//glfwWindowHint(GLFW_RED_BITS, 0);
	window = glfwCreateWindow((int)windowDim.x, (int)windowDim.y, "GLFW WINDOW", nullptr, nullptr);
    if (!window)
		throw std::runtime_error("Failed when trying to open window\n");

    glfwSetWindowPos(window, 200, 200);
	glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    GLenum e = glewInit();
    if ( e != GLEW_OK )
        throw std::runtime_error("glew init failed\n");

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);

	// allow shader controlled point size 
	glEnable(GL_POINT_SPRITE);
	glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
	
	glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);

    runningState = new RunningState();
    pendingState = new PendingState();

    stateLUT[runningState->getType()] = runningState;
    stateLUT[pendingState->getType()] = pendingState;

    // game starts in pending state 
    currentState = pendingState;

	simpleShader_p = new Shader();
	simpleShader_p->addStage("./graphics/shaders/simple_p.vert", "", GL_VERTEX_SHADER);
	simpleShader_p->addStage("./graphics/shaders/simple_p.frag", "", GL_FRAGMENT_SHADER);
	simpleShader_p->install();

	simpleShader_pc = new Shader();
	simpleShader_pc->addStage("./graphics/shaders/simple_pc.vert", "", GL_VERTEX_SHADER);
	simpleShader_pc->addStage("./graphics/shaders/simple_pc.frag", "", GL_FRAGMENT_SHADER);
	simpleShader_pc->install();

	simpleShader_pc_instanced = new Shader();
	simpleShader_pc_instanced->addStage("./graphics/shaders/simple_pc_instanced.vert", "", GL_VERTEX_SHADER);
	simpleShader_pc_instanced->addStage("./graphics/shaders/simple_pc_instanced.frag", "", GL_FRAGMENT_SHADER);
	simpleShader_pc_instanced->install();

	lineRenderer = new LineRenderer();
	pointRenderer = new PointRenderer();
	boxRenderer = new BoxPrimitive();
	particleSystem = new ParticleSystem();
}

void Game::run()
{
    double prevTime = glfwGetTime(); 
	
	int running = true;

	float accFPSTime = 0.0f;
	float FPSUpdFreq = 0.5f;
	int frameCount = 0;
	double elapsed = 0.0f;
	float slowDown = 1.0f;

    while(!glfwWindowShouldClose(window)) {
	    double dt = glfwGetTime() - prevTime;
	    prevTime = glfwGetTime();
		//dt *= 0.1f
		accFPSTime += static_cast<float>(dt);
		frameCount++;

		if (accFPSTime > FPSUpdFreq) {
			float fps = frameCount/accFPSTime;
			accFPSTime = 0.0f;
			frameCount = 0;
			std::stringstream s;
			s << "FPS " << fps;
			glfwSetWindowTitle(Game::window, s.str().c_str());
		}

		dt = 0.02f; // TODO: Dont hardcode timestep
		dt *= slowDown;

		//glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        currentState->update(static_cast<float>(dt));
        {
            State::Type newState = currentState->getQueuedState();
            if ( newState != State::NULL_STATE ) 
            {
                currentState->idle();
                currentState = stateLUT[newState];
                currentState->activate();
            }
        }

	    glfwSwapBuffers(Game::window);
		glfwPollEvents();
    }
}

glm::mat4& Game::getPerspMatrix()
{
    static bool created = false;
    static glm::mat4 projMatrix;

    if ( !created ) {
        float aspect =  static_cast<float>(Game::windowDim.x / Game::windowDim.y);
        projMatrix = glm::perspective(60.0f, aspect, 1.0f, 5000.0f);
        created = true;
    }
    return projMatrix;
}

/* Used for rendering 2d stuff in screen space */ 
glm::mat4& Game::getOrthoMatrix()
{
    static bool created = false;
    static glm::mat4 projMatrix;

    if ( !created ) {
        projMatrix = glm::ortho(0.0f, (float)windowDim.x, 0.0f, (float)windowDim.y);
        created = true;
    }
    return projMatrix;
}

State* Game::getCurrentState() { return currentState; }
RunningState* Game::getRunningState() { return runningState; }